package com.redwinter.logkit.sdk.support;

import com.redwinter.logkit.sdk.beans.LogResult;
import com.redwinter.logkit.sdk.context.LogSpELVarContext;
import com.redwinter.logkit.sdk.parser.LogOperation;
import com.redwinter.logkit.sdk.parser.LogOperationExpressionEvaluator;
import com.redwinter.logkit.sdk.parser.LogOperationSource;
import com.redwinter.logkit.sdk.service.LogRecordService;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author goudadong
 * @description 日志切面
 * @datetime 2021-12-14 17:31
 * @email 2360564660@qq.com
 **/
@Slf4j
public abstract class LogAspectSupport implements BeanFactoryAware, InitializingBean {

    private BeanFactory beanFactory;
    private LogOperationSource logRecordOperationSource;

    private final LogOperationExpressionEvaluator evaluator = new LogOperationExpressionEvaluator();
    private final LogRecordService logRecordService;

    protected LogAspectSupport(LogRecordService logRecordService) {
        this.logRecordService = logRecordService;
    }


    protected Object execute(MethodInvocation invoker, Object target, Method method, Object[] args) {
        Object ret = null;
        Class<?> targetClass = getTargetClass(target);
        Collection<LogOperation> operations = this.logRecordOperationSource.getLogOperations(method, targetClass);
        String errorMsg = "";
        long startTime = System.currentTimeMillis();
        try {
            // 执行方法调用
            ret = invoker.proceed();
        } catch (Throwable throwable) {
            errorMsg = throwable.getMessage();
        }
        // 耗时
        long spendTime = System.currentTimeMillis() - startTime;
        if (!CollectionUtils.isEmpty(operations)) {
            LogExecuteContext logExecuteContext = new LogExecuteContext();
            Method targetMethod = (!Proxy.isProxyClass(targetClass) ?
                    AopUtils.getMostSpecificMethod(method, targetClass) :
                    BridgeMethodResolver.findBridgedMethod(method));
            logExecuteContext.setSpendTime(spendTime)
                    .setMethod(method)
                    .setTarget(target)
                    .setTargetMethod(targetMethod)
                    .setTargetClass(targetClass)
                    .setOperations(operations)
                    .setArgs(args)
                    .setRet(ret)
                    .setErrorMsg(errorMsg);
            // 执行解析
            execute(logExecuteContext);
        }
        return ret;
    }

    protected void execute(LogExecuteContext logExecuteContext) {
        LogResult recordResult = logExecuteContext.parser();
        logRecordService.apply(recordResult);
    }

    private Class<?> getTargetClass(Object target) {
        return AopProxyUtils.ultimateTargetClass(target);
    }

    @Data
    @Accessors(chain = true)
    private class LogExecuteContext {
        private Long spendTime;
        private Object target;
        private Method method;
        private Class<?> targetClass;
        private Method targetMethod;
        private Collection<LogOperation> operations;
        private Object[] args;
        private Object ret;
        private String errorMsg;
        private EvaluationContext evaluationContext;
        final String pre = "([a-zA-Z]+[\\w]*)";
        // ^(:?\{#([a-zA-Z]+[\w]*)(\.)?([a-zA-Z]+[\w]*)?})$|^#([a-zA-Z]+[\w]*)(\.)?([a-zA-Z]+[\w]*)?$
        final String exp = "^(:?\\{#" + pre + "(\\.)?" + pre + "?})$"
                + "|^#" + pre + "(\\.)?" + pre + "?$";

        Pattern pattern = Pattern.compile(exp);

        public void createEvaluationContext() {
            this.evaluationContext = evaluator.createEvaluationContext(method, args, target, targetClass, targetMethod, ret, beanFactory, errorMsg);
        }

        public LogResult parser() {
            createEvaluationContext();
            LogResult result = new LogResult();
            log.debug("方法执行结果：{}", ret);
            for (LogOperation operation : operations) {
                if (checkCondition(operation)) {
                    result.setData(apply(checkHasSpEL(operation.getData()), parseExpression(operation.getData())));
                }
                result.setBizType(apply(checkHasSpEL(operation.getBizType()), operation.getBizType()).toString());
            }
            result.setCreateTime(new Date())
                    .setErrorMsg(errorMsg)
                    .setSpendTime(spendTime)
                    .setTargetMethod(targetMethod.getName())
                    .setTargetClass(targetClass.getName())
                    .setFlag(StringUtils.isEmpty(errorMsg));
            return result;
        }

        private boolean checkHasSpEL(String expression) {
            return pattern.matcher(expression).matches();

        }

        private boolean checkCondition(LogOperation operation) {
            if (!StringUtils.isEmpty(operation.getCondition())) {
                return evaluator.condition(operation.getCondition(), evaluationContext);
            }
            return true;
        }

        public Object apply(boolean has, String expression) {
            if (has) {
                Object value = evaluator.getValue(expression, evaluationContext, Object.class);
                log.debug("解析出来的结果：{}", value);
                return value;
            }
            if (expression.contains("{") || expression.contains("#")) {
                log.error("SpEL表达式不合法，格式为：【{#xx}或者#xx】或者【{#xx.xx} 或者#xx.xx】，当前expression为：{}", expression);
                log.error("其中xx遵循java变量定义规范的格式！");
            }
            log.debug("没有解析到SpEL表达式，expression:{}", expression);
            return expression;
        }

        private String parseExpression(String expression) {
            Pattern compile = Pattern.compile(pre);
            Matcher matcher = compile.matcher(expression);
            if (matcher.find()) {
                String name = matcher.group();
                String relName = LogSpELVarContext.getRelName(name);
                if (!StringUtils.isEmpty(relName)) {
                    expression = expression.replaceFirst(pre, relName);
                }
            }
            return expression;
        }
    }


    public void setLogRecordOperationSource(LogOperationSource logRecordOperationSource) {
        this.logRecordOperationSource = logRecordOperationSource;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public void afterPropertiesSet() throws Exception {

    }
}
